# -*- coding: UTF-8 -*-
from copy import copy
from math import ceil

class InvalidPage(Exception):
    pass

class ObjectPaginator:
    """
    Cette classe facilite les travaux de pagination. Passez-lui un module
    (c'est-à-dire un objet possédant des méthodes get_count() et get_list()), un
    dictionnaire d'arguments à passer à ces méthodes et le nombre d'objets
    que vous voulez par page. Lisez ensuite les propriétés hits et pages
    pour avoir une idée du nombre de pages obtenues. Appelez la méthode
    get_page en spécifiant un numéro de page (les numéros de page commencent
    à 0) pour obtenir en retour une liste des objets appartenant à cette
    page.

    Enfin, vous pouvez vérifier si une page donnée possède une page suivante/précédente
    grâce aux méthodes has_next_page(numéro_page) et has_previous_page(numéro_page)
    (respectivement).
    """
    def __init__(self, module, args, num_per_page, count_method='get_count', list_method='get_list'):
        self.module, self.args = module, args
        self.num_per_page = num_per_page
        self.count_method, self.list_method = count_method, list_method
        self._hits, self._pages = None, None
        self._has_next = {} # Fait office de cache pour les informations du type numéro_page -> a_une_page_suivante?

    def get_page(self, page_number):
        try:
            page_number = int(page_number)
        except ValueError:
            raise InvalidPage
        if page_number < 0:
            raise InvalidPage
        args = copy(self.args)
        args['offset'] = page_number * self.num_per_page
        # On va chercher un enregistrement supplémentaire, et c'est l'existence de cet
        # enregistrement supplémentaire qui va déterminer si la page recherchée possède
        # une page suivante ou pas.
        args['limit'] = self.num_per_page + 1
        object_list = getattr(self.module, self.list_method)(**args)
        if not object_list:
            raise InvalidPage
        self._has_next[page_number] = (len(object_list) > self.num_per_page)
        return object_list[:self.num_per_page]

    def has_next_page(self, page_number):
        "Est-ce que la page numéro $page_number possède une page 'suivante'?"
        if not self._has_next.has_key(page_number):
            if self._pages is None:
                args = copy(self.args)
                args['offset'] = (page_number + 1) * self.num_per_page
                args['limit'] = 1
                object_list = getattr(self.module, self.list_method)(**args)
                self._has_next[page_number] = (object_list != [])
            else:
                self._has_next[page_number] = page_number < (self.pages - 1)
        return self._has_next[page_number]

    def has_previous_page(self, page_number):
        return page_number > 0

    def _get_hits(self):
        if self._hits is None:
            order_args = copy(self.args)
            if order_args.has_key('ordering_tuple'):
                del order_args['ordering_tuple']
            if order_args.has_key('select_related'):
                del order_args['select_related']
            self._hits = getattr(self.module, self.count_method)(**order_args)
        return self._hits

    def _get_pages(self):
        if self._pages is None:
            self._pages = int(ceil(self.hits / float(self.num_per_page)))
        return self._pages

    hits = property(_get_hits)
    pages = property(_get_pages)
